package tech.mhuang.interchan.generator.util;

import freemarker.template.Configuration;
import freemarker.template.DefaultObjectWrapper;
import freemarker.template.Template;
import freemarker.template.TemplateException;
import tech.mhuang.interchan.generator.dto.GeneratorTableDTO;
import tech.mhuang.interchan.generator.manager.mysql.MysqlManager;
import tech.mhuang.pacebox.core.dict.BasicDict;
import tech.mhuang.pacebox.core.file.FileUtil;
import tech.mhuang.pacebox.core.io.IOUtil;
import tech.mhuang.pacebox.core.util.StringUtil;
import tech.mhuang.pacebox.springboot.core.spring.properties.RelaxedPropertyResolver;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

/**
 * 模板生成工具类
 */
public class GeneratorUtil {

    private static final BasicDict templateDict = new BasicDict();

    //TODO 后续自定义模板进行代码生成
    static {
        templateDict.set("entity", "template/entity.ftl")
                .set("service", "template/service.ftl")
                .set("mapper", "template/mapper.ftl")
                .set("dto", "template/dto.ftl")
                .set("xml", "template/xml.ftl")
                .set("controller", "template/controller.ftl")
                .set("service" + File.separator + "impl", "template/serviceImpl.ftl");
    }

    /**
     * 代码生成
     *
     * @param generatorDTOList 需要生成的字段
     * @return 返回生成的路径
     */
    public static String generator(List<GeneratorTableDTO> generatorDTOList, RelaxedPropertyResolver resolver) throws IOException, TemplateException {
        Configuration cfg = new Configuration(Configuration.VERSION_2_3_28);
        cfg.setClassForTemplateLoading(GeneratorUtil.class, "/");
        cfg.setObjectWrapper(new DefaultObjectWrapper(Configuration.VERSION_2_3_28));
        String id = IdGenerator.nextId();
        ZipOutputStream zipStream = new ZipOutputStream(new FileOutputStream(resolver.getProperty("save_generator_path") + id + ".zip"));
        String sourceUrl = resolver.getProperty("temp_generator_path") + id + File.separator;
        FileUtil.createDirectory(new File(sourceUrl));
        templateDict.forEach((module, templateUrl) -> {
            String fileTypeCopy = "";
            String first = "";
            if ("xml".equals(module)) {
                fileTypeCopy = "Mapper.xml";
            } else if ("service".equals(module)) {
                first = "I";
                fileTypeCopy = "Service.java";
            } else if (StringUtil.equals(module, "service" + File.separator + "impl")) {
                fileTypeCopy = "ServiceImpl.java";
            } else if (StringUtil.equals(module, "dto")) {
                fileTypeCopy = "DTO.java";
            } else if (StringUtil.equals(module, "entity")) {
                fileTypeCopy = ".java";
            } else {
                fileTypeCopy = MysqlManager.camelCaseName(module, true) + ".java";
            }

            final String fileType = fileTypeCopy;
            try {
                FileUtil.createDirectory(new File(sourceUrl + module));
                String finalFirst = first;
                generatorDTOList.forEach(tableDTO -> {
                    String generatorFile = sourceUrl + module + File.separator + finalFirst + tableDTO.getEntityClassName() + fileType;
                    try {
                        genFreemarker(generatorFile, (String) templateUrl, tableDTO, cfg);
                    } catch (IOException e) {
                        e.printStackTrace();
                    } catch (TemplateException e) {
                        e.printStackTrace();
                    }
                });

            } catch (IOException e) {
                e.printStackTrace();
            }
        });
        toZip(new File(sourceUrl), zipStream);
        IOUtil.close(zipStream);
        return id;
    }

    private static final int BUFFER_SIZE = 2 * 1024;

    private static void toZip(File sourceFile, ZipOutputStream out) throws IOException {
        compress(sourceFile, out, sourceFile.getName(), true);
    }

    private static void compress(File source, ZipOutputStream zos, String name, boolean KeepDirStructure) throws IOException {
        byte[] buf = new byte[BUFFER_SIZE];
        if (source.isFile()) {
            // 向zip输出流中添加一个zip实体，构造器中name为zip实体的文件的名字
            zos.putNextEntry(new ZipEntry(name));
            // copy文件到zip输出流中
            int len;
            FileInputStream in = new FileInputStream(source);
            while ((len = in.read(buf)) != -1) {
                zos.write(buf, 0, len);
            }
            // Complete the entry
            zos.closeEntry();
        } else {
            File[] listFiles = source.listFiles();
            if (listFiles == null || listFiles.length == 0) {
                // 需要保留原来的文件结构时,需要对空文件夹进行处理
                if (KeepDirStructure) {
                    // 空文件夹的处理
                    zos.putNextEntry(new ZipEntry(name + "/"));
                    // 没有文件，不需要文件的copy
                    zos.closeEntry();
                }
            } else {
                for (File file : listFiles) {
                    // 判断是否需要保留原来的文件结构
                    if (KeepDirStructure) {
                        // 注意：file.getName()前面需要带上父文件夹的名字加一斜杠,
                        // 不然最后压缩包中就不能保留原来的文件结构,即：所有文件都跑到压缩包根目录下了
                        compress(file, zos, name + "/" + file.getName(), KeepDirStructure);
                    } else {
                        compress(file, zos, file.getName(), KeepDirStructure);
                    }
                }
            }
        }
    }

    private static void genFreemarker(String generatorFile, String ftlPath, GeneratorTableDTO root, Configuration cfg) throws IOException, TemplateException {
        File file = new File(generatorFile);
        FileUtil.createFile(file);
        Template temp = cfg.getTemplate(ftlPath);
        Writer out = new OutputStreamWriter(new FileOutputStream(file));
        temp.process(root, out);
        IOUtil.close(out);
    }
}